package pl.wwysocki.gendok;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.log.Log;
import org.odftoolkit.odfdom.doc.OdfDocument;
import org.odftoolkit.odfdom.doc.table.OdfTable;
import org.odftoolkit.odfdom.doc.text.OdfTextParagraph;
import org.odftoolkit.odfdom.doc.text.OdfTextUserFieldDecl;
import org.odftoolkit.odfdom.doc.text.OdfTextUserFieldGet;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

@Name("documentGenerator")
public class DocumentGenerator
{
	@Logger
	Log log;
	private Map<Method, FieldRef> collections = new HashMap<Method, FieldRef>();
	@In(value = "#{facesContext.externalContext}")
	private ExternalContext extCtx;
	@In(value = "#{facesContext}")
	FacesContext facesContext;

	public FieldDecl parseDecl(String name, Object bean)
	{
		Class<?> c = bean.getClass();
		int dot = name.indexOf('.');
		if (dot >= 0)
		{
			String objName = name.substring(0, dot);
			String attrName = name.substring(dot + 1);
			Method getter = null;
			try
			{
				getter = c.getDeclaredMethod("get" + objName, null);
			}
			catch (SecurityException e)
			{
				log.error("Blad - brak dostepu do gettera get{0} w obiekcie {1}.", objName, c.getName());
				e.printStackTrace();
			}
			catch (NoSuchMethodException e)
			{
				log.error("Blad - brak gettera get{0} w obiekcie {1}.", objName, c.getName());
//				e.printStackTrace();
			}
			if (getter == null)
			{
				return null;
			}
			Type type = getter.getGenericReturnType();
			String typeName = type.toString();
			log.info("parseDecl typ {0}", typeName);
			if (typeName.startsWith("java.util.Set")
					|| typeName.equals("interface java.util.Set"))
			{
				return null;
			}
			else
			{
				getter.setAccessible(true);
				Object bean1 = null;
				try
				{
					bean1 = getter.invoke(bean);
				}
				catch (Exception e)
				{
					log.error("Blad wywolania gettera {0} na rzecz obiektu {1}.", getter, objName);
					e.printStackTrace();
				}
				FieldDecl info = parseDecl(attrName, bean1);
				return info;
			}
		}
		else
		{
			Method getter = null;
			try
			{
				getter = c.getDeclaredMethod("get" + name, null);
			}
			catch (Exception e)
			{
				log.error("Blad wyszukania gettera get{0} w klasie {1}.", name, c.getName());
				e.printStackTrace();
			}
			return new FieldDecl(name, getter, bean);
		}
	}

	private void parseRef(OdfTextUserFieldGet element, String name, Object bean)
	{
		Class<?> c = bean.getClass();
		int dot = name.indexOf('.');
		if (dot >= 0)
		{
			String objName = name.substring(0, dot);
			String attrName = name.substring(dot + 1);
			Method getter = null;
			try
			{
				getter = c.getDeclaredMethod("get" + objName, null);
			}
			catch (Exception e)
			{
				log.error("Blad {2} gettera get{0} w obiekcie typu {1}.", objName, bean.getClass().getName(), e.getMessage());
				e.printStackTrace();
			}
			Type type = getter.getGenericReturnType();
			String typeName = type.toString();
			if (typeName.startsWith("java.util.Set"))
			{
				String className = typeName.substring(14, typeName.length() - 1);
				Object bean1 = null;
				try
				{
					c = Class.forName(className);
					bean1 = c.newInstance();
				}
				catch (Exception e)
				{
					log.error("Blad utworzenia obiektu klasy {0}", className);
					e.printStackTrace();
				}
				FieldRef collRef = null;
				if (collections.containsKey(getter))
				{
					collRef = collections.get(getter);
				}
				else
				{
					collRef = new CollRef(objName, element, getter);
					collections.put(getter, collRef);
				}
				collRef.parse(element, attrName, bean1);
			}
			else
			{
				log.info("Proba obslugi przypadku m2o.pole dla {0}", name);
				String className = typeName.substring(6);
				try
				{
					c = Class.forName(className);
				}
				catch (Exception e)
				{
					log.error("Blad utworzenia klasy {0}", typeName);
					e.printStackTrace();
				}
				RelationRef relRef = null;
				if (collections.containsKey(getter))
				{
					relRef = (RelationRef) collections.get(getter);
				}
				else
				{
					relRef = new RelationRef(objName, element, getter);
					collections.put(relRef.getGetter(), relRef);
				}
				relRef.parse(element, attrName, c);
			}
		}
		else
		{
			log.error("Blad - parsowanie nazwy pojedynczego obiektu {0}", name);
		}
	}

	public void generate(Object bean, String nazwaSzablonu, String nazwaDokumentu)
	{
		log.info("generate({0})", bean.getClass().getName());
		ServletContext servletContext = (ServletContext) extCtx.getContext();
		Map<String, FieldDecl> fieldDecls = new HashMap<String, FieldDecl>();
		OdfDocument doc = null;
		try
		{
			log.info("servletContext.getResource = {0}", servletContext.getResource("/" + nazwaSzablonu));
			doc = OdfDocument.loadDocument(servletContext.getResourceAsStream("/" + nazwaSzablonu));
			log.info("szablon {0} zaladowany", nazwaSzablonu);
		}
		catch (Exception e)
		{
			log.error("Blad {0} odczytu szablonu dokumentow {1}.", e.getMessage(), nazwaSzablonu);
			e.printStackTrace();
			return;
		}
		log.info("1a. Ustawianie wartości zadeklarowanych zmiennych - skanowanie zmiennych");
		NodeList nodes = doc.getOfficeBody().getElementsByTagName(OdfTextUserFieldDecl.ELEMENT_NAME.getQName());
		for (int i = 0; i < nodes.getLength(); i++)
		{
			OdfTextUserFieldDecl element = (OdfTextUserFieldDecl) nodes.item(i);
			String name = element.getTextNameAttribute();
			log.info("Przetwarzanie {0}...", name);
			FieldDecl decl = parseDecl(name, bean);
			if (decl != null)
			{
				decl.setElement(element);
				fieldDecls.put(name, decl);
			}
			else
			{
				log.debug("Nazwa {0} odnosi się do kolekcji, omijam", name);
			}
		}
		log.info("1b. Ustawianie wartości zadeklarowanych zmiennych - odczytanie zawartości pól obiektów i zapisanie do deklaracji");
		for (FieldDecl field : fieldDecls.values())
		{
			field.show();
			field.storeValue();
		}
		log.info("2a. Skanowanie odwołań do pól w szablonie");
		NodeList refs = doc.getOfficeBody().getElementsByTagName(OdfTextUserFieldGet.ELEMENT_NAME.getQName());
		for (int i = 0; i < refs.getLength(); i++)
		{
			OdfTextUserFieldGet element = (OdfTextUserFieldGet) refs.item(i);
			String name = element.getTextNameAttribute();
			if (!fieldDecls.containsKey(name))
			{
				log.info("Parsuje {0}", name);
				parseRef(element, name, bean);
			}
		}
		log.info("3. Iterowanie po kolekcjach i budowanie tabel");
		// Usunięcie wiersza szablonu, który potem będzie wypełniany danymi i dodawany do tabeli 
		for (FieldRef field : collections.values())
		{
			field.removeTableRow();
		}
		// Iteracja po kolekcjach i dodawanie wierszy do kolumn
		for (FieldRef field : collections.values())
		{
			log.info("Iteruje obiekt {0}-{1}", field.getName(), field);
			field.generateTables(bean);
		}
		log.info("4. Zapisanie wyniku do pliku");
		try
		{
			HttpServletResponse response = (HttpServletResponse) extCtx.getResponse();
			response.setContentType("application/octet-stream");
			response.addHeader("Content-disposition", "attachment; filename=\"" + nazwaDokumentu + "\"");
			try
			{
				doc.save(response.getOutputStream());
				facesContext.responseComplete();
			}
			catch (Exception e)
			{
				log.error("Blad wyslania odpowiedzi: " + e.toString() + "\n");
			}
			File tempdir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
			String nazwaTempDokum = tempdir.getAbsolutePath() + "/" + nazwaDokumentu;
			log.info("Plik wynikowy zapisany do pliku {0}", nazwaTempDokum);
			doc.save(nazwaTempDokum);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
			doc.close();
		}
	}

	public Log getLog()
	{
		return log;
	}

	public void setLog(Log log)
	{
		this.log = log;
	}
}
